/*
 * Trace task definition.
 * Each task describes a single process we need to trace for file i/o.
 */

#include <sys/ptrace.h>
#include <sys/wait.h>

#include "task.h"
#include "lock.h"
#include "ftrace.h"
#include "syscall.h"



int TraceTask::start(const char* command, char** argv)
{
	int pid = vfork();
	if(pid < 0) {
		FTRACE_LOG("Error: vfork failed\n");
		return -1;
	} else if(pid == 0) {
		
		// Child.
		// Execute command and stop it for tracing.

		long rc = ptrace(PTRACE_TRACEME, 0, NULL, NULL);
		if(rc < 0) {
			FTRACE_LOG("Error: ptrace failed with 0x%lx\n", rc);
			return -1;
		}

		execvp(command, argv);

		// not reached if ok.
		FTRACE_LOG("Error: execvp failed\n");
		return -1;
	}

	// parent.
	// wait for child if it failed to exec and exited.

	int status;
	waitpid(pid, &status, WUNTRACED);

	if(WIFEXITED(status)) {
		FTRACE_LOG("Error: child process failed to exec, task init failed\n");
		return -1;
	}

	this->prepare(pid);

	// check postconditions we've declared.
	FTRACE_ASSERT(this->state() == kStopped);
	FTRACE_LOG("Started tracing task %s (pid %d)\n", command, pid);
	return 0;
}


int TraceTask::attach(pid_t pid) 
{
	long rc = ptrace(PTRACE_ATTACH, pid, NULL, NULL);
	if(rc < 0) {
		FTRACE_LOG("Error: Cannot attach to %d\n", pid);
		return -1;
	}

	int status;
	rc = waitpid(pid, &status, WUNTRACED);
	if(rc < 0) {
		FTRACE_LOG("Error: cannot wait for attached process\n");
		return -1;
	}

	if(WIFEXITED(status)) {
		FTRACE_LOG("Error: traced process terminated!\n");
		return -1;
	}

	this->prepare(pid);

	// check postconditions we've declared.
	FTRACE_ASSERT(this->state() == kStopped);
	FTRACE_LOG("Started tracing pid %d\n", pid);
	return 0;
}


void TraceTask::prepare(pid_t pid) 
{
	// allow for easier disrimination between sigstop generated by syscall entering and everything else.
	ptrace(PTRACE_SETOPTIONS, pid, PTRACE_O_TRACESYSGOOD);
	m_pid = pid;
	m_state = kStopped;
}


void TraceTask::resume(int signo/* = 0*/)
{
	FTRACE_ASSERT(this->state() == kStopped);
	ptrace(PTRACE_SYSCALL, m_pid, &signo, NULL);
}

int TraceTask::trace()
{
	// check precondition.
	FTRACE_ASSERT(this->state() == kStopped);

	// resume stopped task first and the enter signal wait loop.
	this->resume(0);
	
	pid_t pid;
	int status;
	while(1) {
		pid = waitpid(-1, &status, 0);
		this->process_status(status);
		this->resume();
	}

	return 0;
}


void TraceTask::process_status(int status)
{
	if(WIFSTOPPED(status)) {
		int signo = WSTOPSIG(status);
		FTRACE_LOG("Traced process stopped with a signal number %d\n", signo);

		switch(signo) {
		case SIGTRAP:

			int syscall = syscall_get_code(m_pid);
			FTRACE_LOG("Got syscall id 0x%x (%s)\n", syscall, syscall_name(syscall));
			break;
		};
	}
}

